/*
 * Copyright (C) 2015 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.util.concurrent;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.FuturesGetChecked.checkExceptionClassValidity;
import static com.google.common.util.concurrent.FuturesGetChecked.classValueValidator;
import static com.google.common.util.concurrent.FuturesGetChecked.getChecked;
import static com.google.common.util.concurrent.FuturesGetChecked.isCheckedException;
import static com.google.common.util.concurrent.FuturesGetChecked.weakSetValidator;

import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.FuturesGetChecked.GetCheckedTypeValidator;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.acl.NotOwnerException;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.zip.DataFormatException;
import javax.security.auth.RefreshFailedException;

/**
 * Microbenchmark for {@link Futures#getChecked}.
 */
public class FuturesGetCheckedBenchmark {
    private enum Validator {
        NON_CACHING_WITH_CONSTRUCTOR_CHECK(
                nonCachingWithConstructorCheckValidator()), NON_CACHING_WITHOUT_CONSTRUCTOR_CHECK(
                        nonCachingWithoutConstructorCheckValidator()), WEAK_SET(
                                weakSetValidator()), CLASS_VALUE(classValueValidator());

        final GetCheckedTypeValidator validator;

        private Validator(GetCheckedTypeValidator validator) {
            this.validator = validator;
        }
    }

    private enum Result {
        SUCCESS(immediateFuture(new Object())), FAILURE(immediateFailedFuture(new Exception()));

        final Future<Object> future;

        private Result(Future<Object> result) {
            this.future = result;
        }
    }

    private enum ExceptionType {
        CHECKED(IOException.class), UNCHECKED(RuntimeException.class);

        final Class<? extends Exception> exceptionType;

        private ExceptionType(Class<? extends Exception> exceptionType) {
            this.exceptionType = exceptionType;
        }
    }

    private static final ImmutableSet<Class<? extends Exception>> OTHER_EXCEPTION_TYPES =
            ImmutableSet.of(BackingStoreException.class, BrokenBarrierException.class, CloneNotSupportedException.class,
                    DataFormatException.class, ExecutionException.class, GeneralSecurityException.class,
                    InvalidPreferencesFormatException.class, NotOwnerException.class, RefreshFailedException.class,
                    TimeoutException.class, TooManyListenersException.class, URISyntaxException.class);

    @Param
    Validator validator;
    @Param
    Result result;
    @Param
    ExceptionType exceptionType;
    /**
     * The number of other exception types in the cache of known-good exceptions and the number of
     * other {@code ClassValue} entries for the exception type to be tested. This lets us evaluate
     * whether our solution scales to use with multiple exception types and to whether it is
     * affected by other {@code ClassValue} users. Some of the benchmarked implementations don't use
     * one or both of these mechanisms, so they will be unaffected.
     */
    @Param({"0", "1", "12"})
    int otherEntriesInDataStructure;

    final List<ClassValue<?>> retainedReferencesToOtherClassValues = newArrayList();

    @BeforeExperiment
    void addOtherEntries() throws Exception {
        GetCheckedTypeValidator validator = this.validator.validator;
        Class<? extends Exception> exceptionType = this.exceptionType.exceptionType;

        for (Class<? extends Exception> exceptionClass : OTHER_EXCEPTION_TYPES.asList().subList(0,
                otherEntriesInDataStructure)) {
            getChecked(validator, immediateFuture(""), exceptionClass);
        }

        for (int i = 0; i < otherEntriesInDataStructure; i++) {
            ClassValue<Boolean> classValue = new ClassValue<Boolean>() {
                @Override
                protected Boolean computeValue(Class<?> type) {
                    return true;
                }
            };
            classValue.get(exceptionType);
            retainedReferencesToOtherClassValues.add(classValue);
        }
    }

    @Benchmark
    int benchmarkGetChecked(int reps) {
        int tmp = 0;
        GetCheckedTypeValidator validator = this.validator.validator;
        Future<Object> future = this.result.future;
        Class<? extends Exception> exceptionType = this.exceptionType.exceptionType;
        for (int i = 0; i < reps; ++i) {
            try {
                tmp += getChecked(validator, future, exceptionType).hashCode();
            } catch (Exception e) {
                tmp += e.hashCode();
            }
        }
        return tmp;
    }

    private static GetCheckedTypeValidator nonCachingWithoutConstructorCheckValidator() {
        return NonCachingWithoutConstructorCheckValidator.INSTANCE;
    }

    private enum NonCachingWithoutConstructorCheckValidator implements GetCheckedTypeValidator {
        INSTANCE;

        @Override
        public void validateClass(Class<? extends Exception> exceptionClass) {
            checkArgument(isCheckedException(exceptionClass),
                    "Futures.getChecked exception type (%s) must not be a RuntimeException", exceptionClass);
        }
    }

    private static GetCheckedTypeValidator nonCachingWithConstructorCheckValidator() {
        return NonCachingWithConstructorCheckValidator.INSTANCE;
    }

    private enum NonCachingWithConstructorCheckValidator implements GetCheckedTypeValidator {
        INSTANCE;

        @Override
        public void validateClass(Class<? extends Exception> exceptionClass) {
            checkExceptionClassValidity(exceptionClass);
        }
    }
}
